/**
 *  @file HttpPeerBody.cpp
 */
#include "IHttpBody.h"
#include "../../../common/CppMemPool.h"
#include "../../../common/UtilFuncT.h"
#include "../../../io/Stream.h"
#include "../../../convert/Convert.h"
#include "../../../util/zip/Inflater.h"
#include "../HttpException.h"

using namespace cppflib::net::http;
using namespace cppflib::collections;
using namespace cppflib::util::zip;
using namespace cppflib::io;


namespace cppflib
{

namespace net
{

namespace http
{

namespace message
{

/**
 *  Class to read chunked data
 */
class ChunkStream : public ReadStream
{
private:
   u32_t chSize;    ///< size of current chunk
   u32_t readPos;   ///< current read position offset
   BinBuf tempBuf;  ///< temp buffer
   bool isLastChunkRead; ///< flag whether last chunk is read
   Stream *pOrgStream;  ///< original stream
   ArrayByte *pSingleByteBuf; ///< for single byte read

private:
   ChunkStream()
   {
      this->chSize = 0;
      this->readPos = 0;
      this->isLastChunkRead = false;
      this->pOrgStream = NULL;
      this->pSingleByteBuf = NULL;
   }

   ArrayByte *GetSingleByteBuffer()
   {
      if (!pSingleByteBuf) {
         pSingleByteBuf = new ArrayByte(8);
         cppmempool::Retain(pSingleByteBuf);
      }

      return pSingleByteBuf;
   }

   /**
    *  Read until "\r\n" is reached
    */
   void GetOneLine()
   {
      u8_t data;
      tempBuf.ClearBuf();
      
      while (true) {
         int c = pOrgStream->Read();
         data = static_cast<u8_t>(c);
         tempBuf.AppendData(&data, 1);
         if (c == '\r') {
            c = pOrgStream->Read(); 
            if (c == '\n') {
               data = static_cast<u8_t>(c);
               tempBuf.AppendData(&data, 1);
               break;
            }
            else {
               pOrgStream->Seek(-1, Stream::S_CUR);
            }
         }

         if (tempBuf.GetValidLength() > CHUNK_SIZELIMIT)
            throw HttpException(_S("Chunk size line too long error"));

      }
   }

   /**
    *  Last chunk and trailer looks like this
    *
    *  +----------------------------+
    *  | 0 ; option; option;\r\n    |
    *  | optional footer;\r\n       |
    *  | optional footer;\r\n       |
    *  | \r\n                       |
    *  +----------------------------+
    */
   void ReadLastChunk()
   {
      while (true) {
         GetOneLine();
         if (tempBuf.GetValidLength() == 2) {
            u8_t *pBuf = tempBuf.GetRawBuf();
            if (*pBuf == '\r' && *(pBuf + 1) == '\n')
               break;
         }
      }

      isLastChunkRead = true;
   }

   void Reset()
   {
      chSize = 0;
      readPos = 0;
   }

   u32_t BytesLeftInChunk()
   {
      return chSize - readPos;
   }

   void ReadEndOfChunk()
   {
      GetOneLine();
      do {
         if (tempBuf.GetValidLength() != 2) 
            break;

         u8_t *pBuf = tempBuf.GetRawBuf();
         if (*pBuf != '\r' || *(pBuf + 1) != '\n')
            break;
 
         return;

      } while(false);

      throw HttpException(_S("Read end of chunk error"));
   }


   u32_t GetChunkSize()
   {
      return chSize;
   }

   bool IsEmpty()
   {
      return (chSize == 0);
   }

   /**
    *  A chunk appears like this:
    *
    *  +----------------------------+
    *  | xxx; option; option\r\n    |
    *  | data\r\n                   |
    *  +----------------------------+
    *  where xxx is the chunk size.
    *
    *  @return true -- a new chunk info is read
    *  @return false -- last chunk is read
    */
   bool ReadChunkSize()
   {
      GetOneLine();
      CFString chunkSizeLine(reinterpret_cast<const char*>(tempBuf.GetRawBuf()), 
                             static_cast<int>(tempBuf.GetValidLength()));

      chunkSizeLine.Insert(0, _S("0x")); // convert::ToInt32 requires this
      this->chSize = convert::ToInt32(chunkSizeLine);

      if (this->chSize == 0) {    // last chunk has 0 bytes
         ReadLastChunk();
         return false;
      }

      return true;
   }

   /**
    *  @return number of bytes read
    */
   sz_t ReadChunkData(ArrayByte &outBuf, int offset, int count)
   {
      if (count < 0 || offset < 0 || offset >= static_cast<int>(outBuf.GetSize()))
         return 0;

      // use the smaller one
      count = cppflib::Min<int>(count, static_cast<int>(outBuf.GetSize() - offset));

      // read bytes from chunk data
      u32_t bytesToRead = cppflib::Min<u32_t>(count, BytesLeftInChunk());
      int nrRead = pOrgStream->Read(outBuf, offset, static_cast<int>(bytesToRead));
      readPos += nrRead; // advance read pos

      // post update
      if (BytesLeftInChunk() == 0) {
         ReadEndOfChunk();
         Reset();  // prepare for next chunk
      }

      return nrRead;
   }

   bool IsLastChunkRead()
   {
      return isLastChunkRead;
   }

public:
   explicit ChunkStream(Stream *pStream)
   {
      this->chSize = 0;
      this->readPos = 0;
      this->isLastChunkRead = false;
      this->pOrgStream = pStream;
      cppmempool::Retain(this->pOrgStream);
   }
   
   virtual ~ChunkStream()
   {
      cppmempool::Release(this->pOrgStream);

      if (this->pSingleByteBuf)
         cppmempool::Release(this->pSingleByteBuf);
   }

   ///////////////// Stream overriden methods ///////////////////////
   
   virtual int Read(ArrayByte &buffer)
   {
      return Read(buffer, 0, static_cast<int>(buffer.GetSize()));
   }

   virtual int Read(ArrayByte &buffer, int offset, int count)
   {
      if (IsLastChunkRead())
         return 0;

      if (IsEmpty()) {
         if (!ReadChunkSize())  
            return 0;  // last chunk is reached   
      }

      // read chunk data
      return ReadChunkData(buffer, offset, count);
   }

   virtual int Read()
   {
      ArrayByte * pBuf = GetSingleByteBuffer();
      if (Read(*pBuf, 0, 1) > 0)
         return ((*pBuf)[0] & 0xff);

      return -1;
   }

   // these are not supported
   virtual void Close() { return ; }
   virtual i64_t Seek(i64_t offset, Stream::seekorigin_t origin) { return 0; }
};  // end of ChunkStream

/*************************************
 *           HttpPeerBody            *
 *************************************/

HttpPeerBody::HttpPeerBody(void)
{
}

HttpPeerBody::HttpPeerBody(Stream *pStream, u64_t contentLength, bool isChunked, bool isCompressed)
{
   SetContentLength(contentLength);
   SetCurReadPos(0);
   __SetChunked(isChunked);
   __SetCompressed(isCompressed);

   if (isChunked) { 
      this->pStream = new ChunkStream(pStream);  // use a chunk stream
      cppmempool::Retain(this->pStream);
   }
   else {
      this->pStream = pStream;
      cppmempool::Retain(this->pStream);
   }

   if (isCompressed) {  // body is compressed by peer
      this->pInflater = new Inflater(Inflater::AUTODETECT);
      cppmempool::Retain(this->pInflater);
      if (isChunked)
         this->pInflater->SetInputStream(this->pStream);
      else
         this->pInflater->SetInputStream(this->pStream, static_cast<sz_t>(contentLength));
   }
   else {
      this->pInflater = NULL;
   }
   
   this->pSingleByteBuffer = NULL;
}

HttpPeerBody::~HttpPeerBody(void)
{
   cppmempool::Release(pStream);

   if (pInflater)
      cppmempool::Release(pInflater);

   if (pSingleByteBuffer)
      cppmempool::Release(pSingleByteBuffer);
}

void HttpPeerBody::PrepareBuffer()
{
   if (!pSingleByteBuffer) {
      pSingleByteBuffer = new ArrayByte(4);
      cppmempool::Retain(pSingleByteBuffer);
   }
}

int HttpPeerBody::Read(collections::ArrayByte &buffer)
{
   return Read(buffer, 0, static_cast<int>(buffer.GetSize()));
}

int HttpPeerBody::Read(collections::ArrayByte &buffer, int offset, int count)
{
   int nrByteRead = 0;

   if (pInflater) {
      // stream is compressed
      nrByteRead = pInflater->Inflate(buffer, offset, count);
   }
   else {
      // Get directly from underlying stream

      if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
         return 0;
  
      count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

      if (GetContentLength() > 0)  // not chunk transfer
         count = static_cast<int>(cppflib::Min<u64_t>(count, GetContentLength() - GetCurReadPos()));
   
      if (count > 0)  // fetch bytes if not reached the end yet
         nrByteRead = static_cast<sz_t>(pStream->Read(buffer, offset, count));
   }

   AdvanceReadPos(nrByteRead);
   return nrByteRead;
}

int HttpPeerBody::Read()
{
   if (Read(*pSingleByteBuffer, 0, 1) > 0)
      return (*pSingleByteBuffer)[0] & 0xff;

   return -1;
}


} // end of namespace message

} // end of namespace http

} // end of namespace net

} // end of namespace cppflib

